// Copyright 2012 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Platform-specific code for Cygwin goes here. For the POSIX-compatible
// parts, the implementation is in platform-posix.cc.

#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdarg.h>
#include <strings.h> // index
#include <sys/mman.h> // mmap & munmap
#include <sys/time.h>
#include <unistd.h> // sysconf

#include <cmath>

#undef MAP_TYPE

#include "src/base/macros.h"
#include "src/base/platform/platform-posix.h"
#include "src/base/platform/platform.h"
#include "src/base/win32-headers.h"

namespace v8 {
namespace base {

    namespace {

        // The memory allocation implementation is taken from platform-win32.cc.

        DWORD GetProtectionFromMemoryPermission(OS::MemoryPermission access)
        {
            switch (access) {
            case OS::MemoryPermission::kNoAccess:
                return PAGE_NOACCESS;
            case OS::MemoryPermission::kRead:
                return PAGE_READONLY;
            case OS::MemoryPermission::kReadWrite:
                return PAGE_READWRITE;
            case OS::MemoryPermission::kReadWriteExecute:
                return PAGE_EXECUTE_READWRITE;
            case OS::MemoryPermission::kReadExecute:
                return PAGE_EXECUTE_READ;
            }
            UNREACHABLE();
        }

        uint8_t* RandomizedVirtualAlloc(size_t size, DWORD flags, DWORD protect,
            void* hint)
        {
            LPVOID base = nullptr;

            // For executable or reserved pages try to use the address hint.
            if (protect != PAGE_READWRITE) {
                base = VirtualAlloc(hint, size, flags, protect);
            }

            // If that fails, let the OS find an address to use.
            if (base == nullptr) {
                base = VirtualAlloc(nullptr, size, flags, protect);
            }

            return reinterpret_cast<uint8_t*>(base);
        }

    } // namespace

    class CygwinTimezoneCache : public PosixTimezoneCache {
        const char* LocalTimezone(double time) override;

        double LocalTimeOffset(double time_ms, bool is_utc) override;

        ~CygwinTimezoneCache() override { }
    };

    const char* CygwinTimezoneCache::LocalTimezone(double time)
    {
        if (/*std::*/isnan(time))
            return "";
        time_t tv = static_cast<time_t>(std::floor(time / msPerSecond));
        struct tm tm;
        struct tm* t = localtime_r(&tv, &tm);
        if (nullptr == t)
            return "";
        return tzname[0]; // The location of the timezone string on Cygwin.
    }

    double LocalTimeOffset(double time_ms, bool is_utc)
    {
        // On Cygwin, struct tm does not contain a tm_gmtoff field.
        time_t utc = time(nullptr);
        DCHECK_NE(utc, -1);
        struct tm tm;
        struct tm* loc = localtime_r(&utc, &tm);
        DCHECK_NOT_NULL(loc);
        // time - localtime includes any daylight savings offset, so subtract it.
        return static_cast<double>((mktime(loc) - utc) * msPerSecond - (loc->tm_isdst > 0 ? 3600 * msPerSecond : 0));
    }

    // static
    void* OS::Allocate(void* address, size_t size, size_t alignment,
        MemoryPermission access)
    {
        size_t page_size = AllocatePageSize();
        DCHECK_EQ(0, size % page_size);
        DCHECK_EQ(0, alignment % page_size);
        DCHECK_LE(page_size, alignment);
        address = AlignedAddress(address, alignment);

        DWORD flags = (access == OS::MemoryPermission::kNoAccess)
            ? MEM_RESERVE
            : MEM_RESERVE | MEM_COMMIT;
        DWORD protect = GetProtectionFromMemoryPermission(access);

        // First, try an exact size aligned allocation.
        uint8_t* base = RandomizedVirtualAlloc(size, flags, protect, address);
        if (base == nullptr)
            return nullptr; // Can't allocate, we're OOM.

        // If address is suitably aligned, we're done.
        uint8_t* aligned_base = RoundUp(base, alignment);
        if (base == aligned_base)
            return reinterpret_cast<void*>(base);

        // Otherwise, free it and try a larger allocation.
        CHECK(Free(base, size));

        // Clear the hint. It's unlikely we can allocate at this address.
        address = nullptr;

        // Add the maximum misalignment so we are guaranteed an aligned base address
        // in the allocated region.
        size_t padded_size = size + (alignment - page_size);
        const int kMaxAttempts = 3;
        aligned_base = nullptr;
        for (int i = 0; i < kMaxAttempts; ++i) {
            base = RandomizedVirtualAlloc(padded_size, flags, protect, address);
            if (base == nullptr)
                return nullptr; // Can't allocate, we're OOM.

            // Try to trim the allocation by freeing the padded allocation and then
            // calling VirtualAlloc at the aligned base.
            CHECK(Free(base, padded_size));
            aligned_base = RoundUp(base, alignment);
            base = reinterpret_cast<uint8_t*>(
                VirtualAlloc(aligned_base, size, flags, protect));
            // We might not get the reduced allocation due to a race. In that case,
            // base will be nullptr.
            if (base != nullptr)
                break;
        }
        DCHECK_IMPLIES(base, base == aligned_base);
        return reinterpret_cast<void*>(base);
    }

    // static
    bool OS::Free(void* address, const size_t size)
    {
        DCHECK_EQ(0, static_cast<uintptr_t>(address) % AllocatePageSize());
        DCHECK_EQ(0, size % AllocatePageSize());
        USE(size);
        return VirtualFree(address, 0, MEM_RELEASE) != 0;
    }

    // static
    bool OS::Release(void* address, size_t size)
    {
        DCHECK_EQ(0, reinterpret_cast<uintptr_t>(address) % CommitPageSize());
        DCHECK_EQ(0, size % CommitPageSize());
        return VirtualFree(address, size, MEM_DECOMMIT) != 0;
    }

    // static
    bool OS::SetPermissions(void* address, size_t size, MemoryPermission access)
    {
        DCHECK_EQ(0, reinterpret_cast<uintptr_t>(address) % CommitPageSize());
        DCHECK_EQ(0, size % CommitPageSize());
        if (access == MemoryPermission::kNoAccess) {
            return VirtualFree(address, size, MEM_DECOMMIT) != 0;
        }
        DWORD protect = GetProtectionFromMemoryPermission(access);
        return VirtualAlloc(address, size, MEM_COMMIT, protect) != nullptr;
    }

    // static
    bool OS::DiscardSystemPages(void* address, size_t size)
    {
        // On Windows, discarded pages are not returned to the system immediately and
        // not guaranteed to be zeroed when returned to the application.
        using DiscardVirtualMemoryFunction = DWORD(WINAPI*)(PVOID virtualAddress, SIZE_T size);
        static std::atomic<DiscardVirtualMemoryFunction> discard_virtual_memory(
            reinterpret_cast<DiscardVirtualMemoryFunction>(-1));
        if (discard_virtual_memory == reinterpret_cast<DiscardVirtualMemoryFunction>(-1))
            discard_virtual_memory = reinterpret_cast<DiscardVirtualMemoryFunction>(GetProcAddress(
                GetModuleHandle(L"Kernel32.dll"), "DiscardVirtualMemory"));
        // Use DiscardVirtualMemory when available because it releases faster than
        // MEM_RESET.
        DiscardVirtualMemoryFunction discard_function = discard_virtual_memory.load();
        if (discard_function) {
            DWORD ret = discard_function(address, size);
            if (!ret)
                return true;
        }
        // DiscardVirtualMemory is buggy in Win10 SP0, so fall back to MEM_RESET on
        // failure.
        void* ptr = VirtualAlloc(address, size, MEM_RESET, PAGE_READWRITE);
        CHECK(ptr);
        return ptr;
    }

    // static
    bool OS::HasLazyCommits()
    {
        // TODO(alph): implement for the platform.
        return false;
    }

    std::vector<OS::SharedLibraryAddress> OS::GetSharedLibraryAddresses()
    {
        std::vector<SharedLibraryAddresses> result;
        // This function assumes that the layout of the file is as follows:
        // hex_start_addr-hex_end_addr rwxp <unused data> [binary_file_name]
        // If we encounter an unexpected situation we abort scanning further entries.
        FILE* fp = fopen("/proc/self/maps", "r");
        if (fp == nullptr)
            return result;

        // Allocate enough room to be able to store a full file name.
        const int kLibNameLen = FILENAME_MAX + 1;
        char* lib_name = reinterpret_cast<char*>(malloc(kLibNameLen));

        // This loop will terminate once the scanning hits an EOF.
        while (true) {
            uintptr_t start, end;
            char attr_r, attr_w, attr_x, attr_p;
            // Parse the addresses and permission bits at the beginning of the line.
            if (fscanf(fp, "%" V8PRIxPTR "-%" V8PRIxPTR, &start, &end) != 2)
                break;
            if (fscanf(fp, " %c%c%c%c", &attr_r, &attr_w, &attr_x, &attr_p) != 4)
                break;

            int c;
            if (attr_r == 'r' && attr_w != 'w' && attr_x == 'x') {
                // Found a read-only executable entry. Skip characters until we reach
                // the beginning of the filename or the end of the line.
                do {
                    c = getc(fp);
                } while ((c != EOF) && (c != '\n') && (c != '/'));
                if (c == EOF)
                    break; // EOF: Was unexpected, just exit.

                // Process the filename if found.
                if (c == '/') {
                    ungetc(c, fp); // Push the '/' back into the stream to be read below.

                    // Read to the end of the line. Exit if the read fails.
                    if (fgets(lib_name, kLibNameLen, fp) == nullptr)
                        break;

                    // Drop the newline character read by fgets. We do not need to check
                    // for a zero-length string because we know that we at least read the
                    // '/' character.
                    lib_name[strlen(lib_name) - 1] = '\0';
                } else {
                    // No library name found, just record the raw address range.
                    snprintf(lib_name, kLibNameLen,
                        "%08" V8PRIxPTR "-%08" V8PRIxPTR, start, end);
                }
                result.push_back(SharedLibraryAddress(lib_name, start, end));
            } else {
                // Entry not describing executable data. Skip to end of line to set up
                // reading the next entry.
                do {
                    c = getc(fp);
                } while ((c != EOF) && (c != '\n'));
                if (c == EOF)
                    break;
            }
        }
        free(lib_name);
        fclose(fp);
        return result;
    }

    void OS::SignalCodeMovingGC()
    {
        // Nothing to do on Cygwin.
    }

    void OS::AdjustSchedulingParams() { }

} // namespace base
} // namespace v8
